package sk.gtug.pivoplus.malepivo;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;

import sk.gtug.pivoplus.malepivo.utils.Http;
import sk.gtug.pivoplus.malepivo.widget.AddressAdapter;
import sk.gtug.pivoplus.malepivo.widget.AddressSearchView;
import sk.gtug.pivoplus.malepivo.widget.CompassView;
import sk.m217.actionbarcompat.ActionBarListActivity;
import sk.m217.actionbarcompat.MenuItemCompat;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Address;
import android.location.Criteria;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

public class PubListActivity extends ActionBarListActivity implements SensorEventListener, OnItemClickListener {

	private static final String TAG = "PubListActivity";
	private Menu mMenu;
	private List<PubItem> mPubs;
	private PubAdapter mPubAdapter;
	private ProgressDialog mProgressDialog = null;
	private ObtainPubsAsyncTask mObtainPubsAsyncTask = null;

	// location-based stuff
	private Criteria mCriteria;
	private LastLocationFinder mLastLocationFinder;

	/**
	 * My last current location.
	 */
	private Location mLocation = null;

	/**
	 * My current address.
	 */
	private Address mAddress = null;

	private ProgressBar mProgressBar;
	private TextView mLocationTextView;
	private AddressSearchView mAddressSearchView;
	private ProgressDialog mAddressSearchProgressDialog = null;

	private SensorManager mSensorManager;
	private Sensor mSensor;

	private static final String KEY_LOCATION = "location";
	private static final String KEY_ADDRESS = "address";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pub_list);

        //TODO ked nemam net
		//TODO location listener

        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
		mLocationTextView = (TextView) findViewById(R.id.location_text);

	    // This will be used to find the last known location when the application starts.
		mLastLocationFinder = new LastLocationFinder(this);
		mLastLocationFinder.setChangedLocationListener(mOneShotLastLocationUpdateListener);

		mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

		mPubs = new ArrayList<PubItem>();
		mPubAdapter = new PubAdapter(this, mPubs);
		ListView listView = getListView();
		listView.setAdapter(mPubAdapter);
		listView.setOnItemClickListener(this);

		Object object = getLastNonConfigurationInstance();
		if (object != null && object instanceof ObtainPubsAsyncTask) {
			mObtainPubsAsyncTask = (ObtainPubsAsyncTask) object;
			mObtainPubsAsyncTask.attach(this);
			if (!mObtainPubsAsyncTask.isCompleted() && !mObtainPubsAsyncTask.isCancelled()) {
				mProgressDialog = ProgressDialog.show(PubListActivity.this,    
						getString(R.string.please_wait), getString(R.string.searching_pubs),
						true, true, new DialogInterface.OnCancelListener() {
							@Override
							public void onCancel(DialogInterface dialog) {
								if (mObtainPubsAsyncTask != null) {
									mObtainPubsAsyncTask.cancel(false);
								}
							}
				});				
			}
		}
    }

	@Override
	protected void onPause() {
		super.onPause();
		disableLocationUpdates();
		mSensorManager.unregisterListener(this);
	}

	@Override
	protected void onResume() {
		super.onResume();
		getLocationAndUpdatePubs();
		mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_UI);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putParcelable(KEY_LOCATION, mLocation);
		outState.putParcelable(KEY_ADDRESS, mAddress);
	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
		mLocation = savedInstanceState.getParcelable(KEY_LOCATION);
		mAddress = savedInstanceState.getParcelable(KEY_ADDRESS);
		if (mAddress != null) {
			mProgressBar.setVisibility(View.GONE);
			mLocationTextView.setText(AddressAdapter.addrToString(mAddress));
		}
	}

	@Override
	public Object onRetainNonConfigurationInstance() {
		if (mProgressDialog != null) {
			mProgressDialog.dismiss();
		}
		if (mObtainPubsAsyncTask != null) {
			mObtainPubsAsyncTask.detach();
		}
		return mObtainPubsAsyncTask;
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater menuInflater = getMenuInflater();
		menuInflater.inflate(R.menu.publist_options, menu);
		if (mMenu != null) {
			return super.onCreateOptionsMenu(menu);
		}
		mMenu = menu;
		mMenu.findItem(R.id.menu_map).setIntent(MapSearchActivity.createIntent(this));

		MenuItem item = mMenu.findItem(R.id.menu_search);
		MenuItemCompat.setOnActionExpandListener(item,
				new MenuItemCompat.OnActionExpandListenerCompat() {
       		@Override
       		public boolean onMenuItemActionExpand(MenuItem item) {
       			mMenu.findItem(R.id.menu_map).setVisible(false);
       			return true;
       		}
       		@Override
       		public boolean onMenuItemActionCollapse(MenuItem item) {
       			mMenu.findItem(R.id.menu_map).setVisible(true);
       			mAddressSearchView.hideSoftKeyboard();
       			return true;
       		}
		});

		mAddressSearchView = (AddressSearchView) MenuItemCompat.getActionView(item);
		mAddressSearchView.setAddressSearchListener(new AddressSearchView.AddressSearchListener() {
			@Override
			public void onAddressSearchResult(Address address) {
				if (address != null) {
					Toast.makeText(PubListActivity.this, "Make request",
							Toast.LENGTH_SHORT).show();
				} else {
					Toast.makeText(PubListActivity.this, R.string.address_not_found,
							Toast.LENGTH_SHORT).show();
				}
			}
			@Override
			public void onAddressSearchEnd() {
				if (mAddressSearchProgressDialog != null) {
					mAddressSearchProgressDialog.dismiss();
				}
			}
			@Override
			public void onAddressSearchBegin() {
				mAddressSearchView.setLastLocation(mAddress);
				mAddressSearchView.hideSoftKeyboard();
				mAddressSearchProgressDialog = ProgressDialog.show(PubListActivity.this,
						getString(R.string.please_wait), getString(R.string.searching,
								mAddressSearchView.getText()), true, false);
			}
		});

		return super.onCreateOptionsMenu(menu);
	}

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	switch (item.getItemId()) {
			case R.id.menu_search:
				mAddressSearchView.requestFocus();
				mAddressSearchView.showSoftKeyboard();
				break;
    	}

    	return super.onOptionsItemSelected(item);
    }

	@Override
	public void onItemClick(AdapterView<?> arg0, View view, int pos, long id) {
		PubItem pub = mPubs.get(pos);

		Intent podnikDetail = new Intent(this, PodnikDetailActivity.class);
		podnikDetail.putExtra("evalId", pub.getId());
		// TODO: later replace with with valid evalTypeId
		//podnikDetail.putExtra("evalTypeId", stop.getName());
		startActivity(podnikDetail);
	}

	/**
	 * Adapter for bridging between pubs data and ListView.
	 * @author biegleux
	 *
	 */
	private class PubAdapter extends ArrayAdapter<PubItem> {

		private LayoutInflater mInflater;
		private static final float KILOMETER = 1000f;
		private int mOrientation = 0;

		public PubAdapter(Context context, List<PubItem> objects) {
			super(context, 0, objects);
			mInflater = LayoutInflater.from(context);
		}

		public boolean updateDistanceAndDirection(Location location) {
			// TODO ked sa naimplementuje locationlistener,
			// tak volat tuto metodu zakazdym, ked sa zmeni poloha
			if (location != null) {
				for (int i = 0; i < getCount(); i++) {
					PubItem pub = getItem(i);
					pub.setDistance(location);
					pub.setDirection(location);
				}
				notifyDataSetChanged();
				return true;
			}
			return false;
		}

		public boolean updateDirection(int orientation) {
			mOrientation = orientation;
			notifyDataSetChanged();
			return true;
		}
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			ViewHolder viewHolder;
			if (convertView == null) {
				convertView = mInflater.inflate(R.layout.pub_list_item, parent, false);

				viewHolder = new ViewHolder();
				viewHolder.pubName    = (TextView) convertView.findViewById(R.id.pub_name);
				viewHolder.pubAddress = (TextView) convertView.findViewById(R.id.pub_address);
				viewHolder.pubDistance = (TextView) convertView.findViewById(R.id.pub_distance);
				viewHolder.compassView = (CompassView) convertView.findViewById(R.id.compass);
				convertView.setTag(viewHolder);
			} else {
				viewHolder = (ViewHolder) convertView.getTag();
			}

			PubItem pub = getItem(position);
			if (pub != null) {
				viewHolder.pubName.setText(pub.getName());
				viewHolder.pubAddress.setText(pub.getId());
				// TODO neskor upravit, kedze zo servera by mali prist uplne data
				if (mLocation != null && pub.hasCoords()) {
					viewHolder.pubDistance.setVisibility(View.VISIBLE);
					viewHolder.compassView.setVisibility(View.VISIBLE);

					float distance = pub.getDistance();
					String distanceText = (distance < KILOMETER) ?
							getString(R.string.distance_in_meters, distance) : 
							getString(R.string.distance_in_kilometers, (distance / KILOMETER));

					viewHolder.pubDistance.setText(distanceText);
					viewHolder.compassView.setDirection(mOrientation + (int) pub.getDirection());
				} else {
					viewHolder.pubDistance.setVisibility(View.GONE);
					viewHolder.compassView.setVisibility(View.GONE);
				}
			}

			return convertView;
		}

		private class ViewHolder {
			public TextView pubName;
			public TextView pubAddress;
			public TextView pubDistance;
			public CompassView compassView;
		}
	}

	/**
	 * Background task for obtaining pubs.
	 * @author biegleux
	 *
	 */
	static class ObtainPubsAsyncTask extends AsyncTask<Void, Void, List<PubItem>> {

		private PubListActivity mActivity = null;
		private boolean mCompleted = false;
		private static String URL = "http://pluspivo.appspot.com/pivo-plus-rest/pivo/krcmy";

		public ObtainPubsAsyncTask(PubListActivity activity) {
			mActivity = activity;
		}

		@Override
		protected List<PubItem> doInBackground(Void... unused) {
			String jsonString = Http.fetch(URL);
			if (jsonString != null && !isCancelled()) {
				Type listType = new TypeToken<List<PubItem>>(){}.getType();
				try {
					List<PubItem> pubs = new Gson().fromJson(jsonString, listType);
					return pubs;
				} catch (JsonSyntaxException e) {
					e.printStackTrace();
				} catch (JsonParseException e) {
					e.printStackTrace();
				}
			}
			return null;
		}

		@Override
		protected void onPostExecute(List<PubItem> pubs) {
			mCompleted = true;
			if (mActivity != null) {
				mActivity.onObtainPubsCompleted();
			}
		}

		@Override
		protected void onCancelled() {
			mCompleted = true;
		}

		public void detach() {
			mActivity = null;
		}

		public void attach(PubListActivity activity) {
			mActivity = activity;
			if (mCompleted && !isCancelled()) {
				mActivity.onObtainPubsCompleted();
			}
		}

		public boolean isCompleted() {
			return mCompleted;
		}
	}

	/**
	 * Occurs when the ObtainPubsAsyncTask has been completed.
	 */
	private void onObtainPubsCompleted() {
		if (mProgressDialog != null) {
			mProgressDialog.dismiss();
		}
		try {
			List<PubItem> pubs = mObtainPubsAsyncTask.get();
			if (pubs != null) {
				// TODO sort by rating, distance, etc...
				// TODO co ak bude null
				mPubs.clear();
				mPubs.addAll(pubs);
				if (!mPubAdapter.updateDistanceAndDirection(mLocation)) {
					mPubAdapter.notifyDataSetChanged();
				}
				//Collections.sort(mPubs);
			}
		} catch (ExecutionException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (CancellationException e) {
			e.printStackTrace();
		}
	}

	/**
	 * One-off location listener that receives updates from the {@link LastLocationFinder}.
	 * This is triggered where the last known location is outside the bounds of our maximum
	 * distance and latency.
	 */
	protected LocationListener mOneShotLastLocationUpdateListener = new LocationListener() {
		public void onLocationChanged(Location l) {
			updateAddress(l);
			updatePubs(l, Application.DEFAULT_RADIUS, false);//, true);
		}
		public void onProviderDisabled(String provider) {}
		public void onStatusChanged(String provider, int status, Bundle extras) {}
		public void onProviderEnabled(String provider) {}
	};

	/**
	 * Gets the current location and updates address and pubs accordingly.
	 */
	private void getLocationAndUpdatePubs() {
		Location lastKnownLocation = mLastLocationFinder.getLastBestLocation(
				Application.MAX_DISTANCE, System.currentTimeMillis() - Application.MAX_TIME);
		updateAddress(lastKnownLocation);
		updatePubs(lastKnownLocation, Application.DEFAULT_RADIUS, false);
	}

	/**
	 * Obtains address from the given location and updates the address if the last
	 * known location is outside the bounds of our maximum distance and latency.
	 * @param location
	 */
	private void updateAddress(final Location location) {
		if (location == null) {
			mAddress = null;
			mProgressBar.setVisibility(View.VISIBLE);
			mLocationTextView.setText(R.string.waiting_for_location);
			return;
		}
		// Update address if time or distance bounds have been passed or address is not present.
		boolean update = (mAddress == null) || (mLocation == null) || 
				(mLocation.getTime() < System.currentTimeMillis() - Application.MAX_TIME) || 
				(mLocation.distanceTo(location) > Application.MAX_DISTANCE);
		if (update) {
			// NOTE: Quite simple operation, application restart is not handled
			// while thread is running.
			new Thread() {
				public void run() {
					Geocoder geocoder = new Geocoder(PubListActivity.this, Locale.getDefault());
					try {
						final List<Address> addresses = geocoder.getFromLocation(
								location.getLatitude(), location.getLongitude(), 1);
						runOnUiThread(new Runnable() {
							public void run() {
								if (addresses != null && !addresses.isEmpty()) {
									mAddress = addresses.get(0);
									mProgressBar.setVisibility(View.GONE);
									mLocationTextView.setText(AddressAdapter.addrToString(mAddress));
								}
							}
						});
					} catch (IOException e) {
						// TODO nastavit spat staru adresu? alebo si pockat na dalsi update (zatial neimpl.)
						e.printStackTrace();
					}
				}
			}.start();
		}
	}

	/**
	 * Updates pubs within the specified radius of the specified location if the last
	 * known location is outside the bounds of our maximum distance and latency.
	 * @param location
	 * @param radius
	 * @param forceUpdate
	 */
	private void updatePubs(Location location, int radius, boolean forceUpdate) {
		// TODO ObtainPubsAsyncTask zatial ziskava vsetky krcmy
		if (location == null) {
			mLocation = null;
			return;
		}
		boolean update = forceUpdate;
		// Update pubs if time or distance bounds have been passed.
		if (!update) {
			update = (mLocation == null) ||
					(mLocation.getTime() < System.currentTimeMillis() - Application.MAX_TIME) || 
					(mLocation.distanceTo(location) > Application.MAX_DISTANCE);
		}
		if (update) {
			mLocation = location;
			if (mObtainPubsAsyncTask != null && !mObtainPubsAsyncTask.isCompleted()) {
				mObtainPubsAsyncTask.cancel(false);
			}
			mObtainPubsAsyncTask = new ObtainPubsAsyncTask(this);
			mObtainPubsAsyncTask.execute();
			// Show progress dialog.
			// TODO otestovat spravanie
			// TODO asi musim dismiss, lebo jeho onCancel ukazuje este na povodny task
			if (mProgressDialog == null || !mProgressDialog.isShowing()) {
				mProgressDialog = ProgressDialog.show(PubListActivity.this,    
						getString(R.string.please_wait), getString(R.string.searching_pubs),
						true, true, new DialogInterface.OnCancelListener() {
							@Override
							public void onCancel(DialogInterface dialog) {
								if (mObtainPubsAsyncTask != null) {
									mObtainPubsAsyncTask.cancel(false);
								}
							}
						});
			} else {
				// Update AsyncTask reference.
				mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
					@Override
					public void onCancel(DialogInterface dialog) {
						if (mObtainPubsAsyncTask != null) {
							mObtainPubsAsyncTask.cancel(false);
						}
					}
				});
			}
		}
	}

	private void disableLocationUpdates() {
		if (isFinishing()) {
			mLastLocationFinder.cancel();
		}
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}

	@Override
	public void onSensorChanged(SensorEvent event) {
		int orientation = (int) event.values[0];
		switch (((WindowManager) getSystemService(Activity.WINDOW_SERVICE))
				.getDefaultDisplay().getRotation()) {
			case Surface.ROTATION_0:
				orientation += 0;
				break;
			case Surface.ROTATION_90:
				orientation += 90;
				break;
			case Surface.ROTATION_270:
				orientation -= 90;
				break;
		}
		mPubAdapter.updateDirection(360 - orientation);
	}

	public static Intent createIntent(Context context) {
		Intent intent = new Intent(context, PubListActivity.class);
		intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
		intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
		return intent;
	}
}
